// -------------------------------------------------
// BlinKit - BlinKit Library
// -------------------------------------------------
//   File Name: http_loader_task.cpp
// Description: HTTPLoaderTask Class
//      Author: Ziming Li
//     Created: 2019-03-21
// -------------------------------------------------
// Copyright (C) 2019 MingYang Software Technology.
// -------------------------------------------------

#include "./http_loader_task.h"

#include "bkcommon/buffer_impl.hpp"
#include "bkcommon/response_impl.h"
#include "blinkit/app/app_impl.h"
#include "blinkit/loader/loader_thread.h"
#include "blinkit/crawler/cookie_jar_impl.h"
#include "blinkit/crawler/crawler_impl.h"
#include "blinkit/crawler/hijack_response.h"
#include "blinkit/blink/public/platform/WebURLLoaderClient.h"
#include "blinkit/blink/renderer/platform/HTTPNames.h"
#include "blinkit/blink/renderer/platform/network/ResourceResponse.h"
#include "chromium/net/http/http_util.h"
#include "third_party/zed/include/zed/net/http_constants.hpp"

using namespace zed::http;

namespace BlinKit {

static zed::mutex g_poolLock;
static std::unordered_set<HTTPLoaderTask *> g_pendingTaskPool;

static HTTPLoaderTask* GetTask(void *userData)
{
    HTTPLoaderTask *task = reinterpret_cast<HTTPLoaderTask *>(userData);
    if (auto _ = g_poolLock.guard())
    {
        auto it = g_pendingTaskPool.find(task);
        if (g_pendingTaskPool.end() == it)
            return nullptr;

        g_pendingTaskPool.erase(it);
    }
    return task;
}

HTTPLoaderTask::HTTPLoaderTask(const ResourceRequest &request, WebURLLoader *loader, const std::shared_ptr<WebTaskRunner> &taskRunner, WebURLLoaderClient *client)
    : LoaderTask(loader, taskRunner, client), m_resourceRequest(request)
{
}

HTTPLoaderTask::~HTTPLoaderTask(void)
{
    if (nullptr != m_response)
        m_response->Release();
}

int HTTPLoaderTask::CancelWork(void)
{
    if (isMainThread())
        DoCancel();
    else
        m_taskRunner->postTask(BLINK_FROM_HERE, std::bind(&HTTPLoaderTask::DoCancel, this));
    return BK_ERR_SUCCESS;
}

void HTTPLoaderTask::Cleanup(void)
{
    auto _ = g_poolLock.guard();
    for (HTTPLoaderTask *task : g_pendingTaskPool)
        delete task;
    g_pendingTaskPool.clear();
}

void HTTPLoaderTask::CommitHijackedResponse(void)
{
    std::string currentURL;
    m_response->GetData(BK_RESPONSE_CURRENT_URL, BufferImpl::Wrap(currentURL));

    if (CookieJarImpl *cookieJar = m_resourceRequest.Crawler()->GetCookieJar(false))
    {
        size_t n = m_response->CookiesCount();
        for (size_t i = 0; i < n; ++i)
        {
            std::string cookie;
            m_response->GetCookie(i, BufferImpl::Wrap(cookie));
            cookieJar->Save(currentURL.c_str(), cookie.c_str());
        }
    }

    ResourceResponse response;
    response.setURL(KURL(currentURL));
    PopulateResourceResponse(response);

    std::string_view body(reinterpret_cast<const char *>(m_response->Content()), m_response->ContentLength());
    CommitResponse(response, body);
    delete this;
}

int HTTPLoaderTask::ContinueWorking(void)
{
    if (isMainThread())
        CommitHijackedResponse();
    else
        m_taskRunner->postTask(BLINK_FROM_HERE, std::bind(&HTTPLoaderTask::CommitHijackedResponse, this));
    return BK_ERR_SUCCESS;
}

BkRequest HTTPLoaderTask::CreateRequest(const std::string &URL)
{
    BkRequestClient client = { 0 };
    client.SizeOfStruct = sizeof(BkRequestClient);
    client.UserData = this;
    client.RequestComplete = RequestCompleteImpl;
    client.RequestFailed = RequestFailedImpl;
    client.RequestRedirect = RequestRedirectImpl;
    return BkCreateRequest(URL.c_str(), &client);
}

void HTTPLoaderTask::DoCancel(void)
{
    m_resourceRequest.Crawler()->CancelLoading();
    delete this;
}

AtomicString HTTPLoaderTask::GetResponseHeader(const AtomicString &name) const
{
    std::string ret;
    int r = m_response->GetHeader(name.stdUTF8().c_str(), BufferImpl::Wrap(ret));
    return BK_ERR_SUCCESS == r ? AtomicString::fromStdUTF8(ret) : nullAtom;
}

int HTTPLoaderTask::PerformHTTPRequest(void)
{
    const std::string &URL = m_resourceRequest.url().spec();
    if (ProcessHijackRequest(URL))
    {
        m_taskRunner->postTask(BLINK_FROM_HERE,
            std::bind(&HTTPLoaderTask::CommitHijackedResponse, this)
        );
        return BK_ERR_CANCELLED;
    }

    BkRequest request = CreateRequest(URL);
    if (nullptr == request)
    {
        ASSERT(nullptr != request);
        return BK_ERR_UNKNOWN;
    }

    BkSetRequestMethod(request, m_resourceRequest.httpMethod().stdUTF8().c_str());
    for (const auto &it : m_resourceRequest.httpHeaderFields())
    {
        std::string name = it.key.stdUTF8();
        std::string value = it.value.stdUTF8();
        BkSetRequestHeader(request, name.c_str(), value.c_str());
    }

    CrawlerImpl *crawler = m_resourceRequest.Crawler();
    if (CookieJarImpl *cookieJar = crawler->GetCookieJar(false))
    {
        std::string cookies = cookieJar->Get(URL.c_str());
        if (!cookies.empty())
            BkSetRequestHeader(request, header_names::Cookie, cookies.c_str());
    }
    crawler->ModifyRequest(URL.c_str(), request);

    if (auto _ = g_poolLock.guard())
        g_pendingTaskPool.emplace(this);
    return BkPerformRequest(request, nullptr);
}

void HTTPLoaderTask::PopulateHijackedResponse(const std::string &URL, const std::string &hijack)
{
    FakeResponse *response = new FakeResponse(URL);
    ASSERT(false); // BKTODO:
#if 0
    switch (m_resourceRequest.GetHijackType())
    {
        case HijackType::kScript:
            response->SetContentType("application/javascript; charset=utf-8");
            break;
        default:
            NOTREACHED();
    }
    response->HijackBody(hijack.data(), hijack.length());
#endif

    ASSERT(nullptr == m_response);
    m_response = response;
}

void HTTPLoaderTask::PopulateResourceResponse(ResourceResponse &response) const
{
    std::string currentURL;
    m_response->GetData(BK_RESPONSE_CURRENT_URL, BufferImpl::Wrap(currentURL));
    response.setURL(KURL(currentURL));

    response.setHTTPStatusCode(m_response->StatusCode());

    std::string contentType;
    m_response->GetHeader(header_names::ContentType, BufferImpl::Wrap(contentType));

    bool hasCharset = false;
    std::string mimeType, charset;
    net::HttpUtil::ParseContentType(contentType, &mimeType, &charset, &hasCharset, nullptr);
    response.setMimeType(AtomicString::fromStdUTF8(mimeType));
    if (hasCharset)
        response.setTextEncodingName(AtomicString::fromStdUTF8(charset));
}

int HTTPLoaderTask::PopulateResponse(ResourceResponse &resourceResponse, std::string_view &body) const
{
    CrawlerImpl *crawler = m_resourceRequest.Crawler();

    switch (m_resourceRequest.GetType())
    {
        case ResourceRequest::Type::MainHTML:
        {
            if (crawler->ProcessRequestComplete(m_response, const_cast<HTTPLoaderTask *>(this)))
                return BK_ERR_CANCELLED;
            break;
        }

        default:
            ASSERT(ResourceRequest::Type::Script == m_resourceRequest.GetType());
            crawler->HijackResponse(m_response);
    }

    PopulateResourceResponse(resourceResponse);
    body = std::string_view(reinterpret_cast<const char *>(m_response->Content()), m_response->ContentLength());
    return BK_ERR_SUCCESS;
}

bool HTTPLoaderTask::ProcessHijackRequest(const std::string &URL)
{
    ASSERT(isMainThread());

    if (ResourceRequest::Type::Script != m_resourceRequest.GetType())
        return false;

    std::string hijack;
    if (!m_resourceRequest.Crawler()->HijackScript(URL.c_str(), hijack))
        return false;

    PopulateHijackedResponse(URL, hijack);
    return true;
}

void HTTPLoaderTask::RequestComplete(BkResponse response)
{
    ASSERT(nullptr == m_response);
    m_response = response->Retain();
    Finish(BK_ERR_SUCCESS);
}

void BKAPI HTTPLoaderTask::RequestCompleteImpl(BkResponse response, void *userData)
{
    if (HTTPLoaderTask *task = GetTask(userData))
        task->RequestComplete(response);
}

void HTTPLoaderTask::RequestFailed(int errorCode)
{
    BKLOG("HTTPLoaderTask::RequestFailed: %d.", errorCode);
    m_errorCode = errorCode;
    Finish(errorCode);
}

void BKAPI HTTPLoaderTask::RequestFailedImpl(int errorCode, void *userData)
{
    if (HTTPLoaderTask *task = GetTask(userData))
        task->RequestFailed(errorCode);
}

bool HTTPLoaderTask::RequestRedirect(BkResponse response, BkRequest request)
{
    size_t n = response->CookiesCount();
    if (n > 0)
    {
        ZLOG("TODO: Process {} cookies!", n);
#if 0
        if (CookieJarImpl *cookieJar = m_crawler->GetCookieJar(false))
        {
            std::unique_lock<CookieJarImpl> lock(*cookieJar);
            const char *URL = m_request.Url().spec().c_str();
            for (size_t i = 0; i < n; ++i)
            {
                std::string cookie;
                response->GetCookie(i, BufferImpl::Wrap(cookie));
                cookieJar->Set(cookie.c_str(), URL);
            }

            std::string cookies = cookieJar->Get(URL);
            if (!cookies.empty())
                BkSetRequestHeader(request, Strings::HttpHeader::Cookie, cookies.c_str());
        }
#endif
    }
    return true;
}

bool_t BKAPI HTTPLoaderTask::RequestRedirectImpl(BkResponse response, BkRequest request, void *userData)
{
    return reinterpret_cast<HTTPLoaderTask *>(userData)->RequestRedirect(response, request);
}

bool HTTPLoaderTask::ShouldIgnoreHTTPStatusCodeErrors(void) const
{
    return m_resourceRequest.GetType() == ResourceRequest::Type::MainHTML;
}

} // namespace BlinKit
